// --------------------------------------------------------------------------------------------------------------------
// <copyright file="FeedPlugin.cs" company="Open Trader">
//   Copyright (c) David Denis (david.denis@systemathics.com)
// </copyright>
// <summary>
//   |  Open Trader - The Open Source Systematic Trading Platform
//   |
//   |  This program is free software: you can redistribute it and/or modify
//   |  it under the terms of the GNU General Public License as published by
//   |  the Free Software Foundation, either version 2 of the License, or
//   |  (at your option) any later version.
//   |
//   |  This program is distributed in the hope that it will be useful,
//   |  but WITHOUT ANY WARRANTY; without even the implied warranty of
//   |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   |  GNU General Public License for more details.
//   |
//   |  You should have received a copy of the GNU General Public License
//   |  along with this program.  If not, see http://www.gnu.org/licenses
//   |
//   |  Up to date informations about Open Trader can be found at :
//   |    http://opentrader.org
//   |    http://opentrader.codeplex.com
//   |
//   |  For professional services, please visit us at :
//   |    http://www.systemathics.com
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Org.OpenTrader.Framework.Forge
{
    #region Using Directives

    using System;
    using System.Collections.Generic;

    using Org.OpenTrader.Framework.Forge.Events;
    using Org.OpenTrader.Framework.Forge.Interfaces;
    using Org.OpenTrader.Framework.LiveObjects.Interfaces;

    #endregion

    /// <summary>
    /// The feed plugin.
    /// </summary>
    public abstract class FeedPlugin : LayerPlugin, IFeedPlugin
    {
        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="FeedPlugin"/> class.
        /// </summary>
        /// <param name="descriptor">
        /// The descriptor.
        /// </param>
        /// <param name="container">
        /// The container.
        /// </param>
        protected FeedPlugin(ILiveObjectDescriptor descriptor, ILiveObjectContainer container)
            : base(descriptor, container)
        {
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets EmitedEventsType.
        /// </summary>
        public abstract EEmitedEventsType EmitedEventsType { get; }

        /// <summary>
        /// Gets a value indicating whether NeedResubscriptionIfDisconnected.
        /// </summary>
        public abstract bool NeedResubscriptionIfDisconnected { get; }

        /// <summary>
        /// Gets SubscriptionConfigurationSchema.
        /// </summary>
        public string SubscriptionConfigurationSchema
        {
            get
            {
                return "SubscriptionConfigurationSchema";
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// The fire event market book updates.
        /// </summary>
        /// <param name="updates">
        /// The updates.
        /// </param>
        /// <param name="isSnapshot">
        /// The is snapshot.
        /// </param>
        /// <param name="handler">
        /// The handler.
        /// </param>
        /// <exception cref="Exception">
        /// </exception>
        public void RaiseEventMarketBookCells(MarketBookCell[] updates, bool isSnapshot, RemotableEventHandler<FeedEventArgs> handler)
        {
            if (this.EmitedEventsType == EEmitedEventsType.BookEvents)
            {
                handler.RaiseEvent(this, new FeedEventArgs.MarketBookUpdate(this.Descriptor.ID, updates, isSnapshot));
                return;
            }

            throw new Exception("You should only emit MarketBookCell if and only if EmitedEventsType == EEmitedEventsType.BookEvents");
        }

        /// <summary>
        /// The fire event market field updates.
        /// </summary>
        /// <param name="updates">
        /// The updates.
        /// </param>
        /// <param name="isSnapshot">
        /// The is snapshot.
        /// </param>
        /// <param name="handler">
        /// The handler.
        /// </param>
        public void RaiseEventMarketFieldCells(MarketField[] updates, bool isSnapshot, RemotableEventHandler<FeedEventArgs> handler)
        {
            handler.RaiseEvent(this, new FeedEventArgs.MarketFieldUpdate(this.Descriptor.ID, updates, isSnapshot));
        }

        /// <summary>
        /// The fire event market quote updates.
        /// </summary>
        /// <param name="updates">
        /// The updates.
        /// </param>
        /// <param name="isSnapshot">
        /// The is snapshot.
        /// </param>
        /// <param name="handler">
        /// The handler.
        /// </param>
        /// <exception cref="Exception">
        /// </exception>
        public void RaiseEventMarketQuotes(MarketQuote[] updates, bool isSnapshot, RemotableEventHandler<FeedEventArgs> handler)
        {
            if (this.EmitedEventsType == EEmitedEventsType.QuoteEvents)
            {
                handler.RaiseEvent(this, new FeedEventArgs.MarketQuoteUpdate(this.Descriptor.ID, updates, isSnapshot));
                return;
            }

            throw new Exception("You should only emit MarketQuote if and only if EmitedEventsType == EEmitedEventsType.QuoteEvents");
        }

        #endregion

        #region Implemented Interfaces

        #region IFeedPlugin

        /// <summary>
        /// The start subscription.
        /// </summary>
        /// <param name="wiring">
        /// The wiring.
        /// </param>
        /// <param name="handler">
        /// The handler.
        /// </param>
        /// <returns>
        /// </returns>
        public abstract Status StartSubscription(IFeedWiring wiring, RemotableEventHandler<FeedEventArgs> handler);

        /// <summary>
        /// The stop subscription.
        /// </summary>
        /// <param name="wiring">
        /// The wiring.
        /// </param>
        /// <param name="handler">
        /// The handler.
        /// </param>
        /// <returns>
        /// </returns>
        public abstract Status StopSubscription(IFeedWiring wiring, RemotableEventHandler<FeedEventArgs> handler);

        #endregion

        #endregion

        /// <summary>
        /// This class is a store for handlers. Each handler is stored for a particular wiring.
        /// </summary>
        public class HandlersStore
        {
            #region Constants and Fields

            /// <summary>
            /// The handlers, each one being kept for a particular subscription key.
            /// </summary>
            private readonly IDictionary<IFeedWiring, RemotableEventHandler<FeedEventArgs>> handlers =
                new Dictionary<IFeedWiring, RemotableEventHandler<FeedEventArgs>>();

            #endregion

            #region Public Methods

            /// <summary>
            /// The clear handlers.
            /// </summary>
            public void Clear()
            {
                this.handlers.Clear();
            }

            /// <summary>
            /// The get handler.
            /// </summary>
            /// <param name="feedWiring">
            /// The subscription Key.
            /// </param>
            /// <returns>
            /// </returns>
            public RemotableEventHandler<FeedEventArgs> GetHandler(IFeedWiring feedWiring)
            {
                if (this.handlers.ContainsKey(feedWiring))
                {
                    return this.handlers[feedWiring];
                }

                return null;
            }

            /// <summary>
            /// The has handler.
            /// </summary>
            /// <param name="feedWiring">
            /// The feed wiring.
            /// </param>
            /// <returns>
            /// The has handler.
            /// </returns>
            public bool HasHandler(IFeedWiring feedWiring)
            {
                return this.handlers.ContainsKey(feedWiring);
            }

            /// <summary>
            /// The set handler.
            /// </summary>
            /// <param name="feedWiring">
            /// The subscription Key.
            /// </param>
            /// <param name="handler">
            /// The handler.
            /// </param>
            public void SetHandler(IFeedWiring feedWiring, RemotableEventHandler<FeedEventArgs> handler)
            {
                this.handlers[feedWiring] = handler;
            }

            /// <summary>
            /// The unset handler.
            /// </summary>
            /// <param name="feedWiring">
            /// The feed wiring.
            /// </param>
            public void UnsetHandler(IFeedWiring feedWiring)
            {
                if (this.handlers.ContainsKey(feedWiring))
                {
                    this.handlers.Remove(feedWiring);
                }
            }

            #endregion
        }

        /// <summary>
        /// Keep forward and backward mappings for each subscription.
        /// Each subscription has :
        /// - An Internal identifier (the associated FeedWiring)
        /// - An External identifier (the identifier used by the external system when it delivers messages for feed updates or failures)
        /// 
        /// Example : Say your external identifier is a string. This string contains the name of the symbol you want to subscribe/unsubscribe (example: "DELL")
        /// 
        /// You'll have :
        /// InternalIdentifier : (IFeedWiring)  { InstrumentID=..., PluginID=..., SubscriptionConfiguration= { ... , Symbol=DELL , ... } }
        /// ExternalIdentifier : (string)       "DELL" 
        ///
        /// On external messages coming in and containing "DELL" you will :
        /// - Decode the message and produce some QuoteUpdate, BookUpdate or FieldUpdate 
        /// - Lookup the associated wiring for "DELL",
        /// - Use the wiring to find the return handler (using HandlersStore, see above)
        /// - Raise the event containing your freshly created update through the handler
        /// 
        /// On synchronous calls (StartSubscription / StopSubscription) you will :
        /// - Check if some mapping is there for the (Internal,External) pair [-&gt; accept to start if none present, accept to stop if some is present]
        /// - Update the mapping (add one if start, remove the existing one if stop)
        /// 
        /// Example : Your external identifier can be any type, for example using QuickFIX you'll have ExternalIdentifierType=MDReqID
        /// </summary>
        /// <typeparam name="ExternalIdentifierType">
        /// The type used on external side to identify a subscription. InternalIdentifierType is IFeedWiring
        /// </typeparam>
        public class SubscriptionMappingStore<ExternalIdentifierType>
        {
            #region Constants and Fields

            /// <summary>
            /// Subscriptions : wiring -> T
            /// </summary>
            private readonly IDictionary<IFeedWiring, ExternalIdentifierType> externalPerInternal = new Dictionary<IFeedWiring, ExternalIdentifierType>();

            /// <summary>
            /// Subscriptions : T -> wiring 
            /// </summary>
            private readonly IDictionary<ExternalIdentifierType, IFeedWiring> internalPerExternal = new Dictionary<ExternalIdentifierType, IFeedWiring>();

            #endregion

            #region Public Methods

            /// <summary>
            /// The clear.
            /// </summary>
            public void Clear()
            {
                this.internalPerExternal.Clear();
                this.externalPerInternal.Clear();
            }

            /// <summary>
            /// The get mapping.
            /// </summary>
            /// <param name="externalSubscriptionIdentifier">
            /// The external subscription identifier.
            /// </param>
            /// <returns>
            /// </returns>
            public IFeedWiring GetMapping(ExternalIdentifierType externalSubscriptionIdentifier)
            {
                if (this.internalPerExternal.ContainsKey(externalSubscriptionIdentifier))
                {
                    return this.internalPerExternal[externalSubscriptionIdentifier];
                }

                return null;
            }

            /// <summary>
            /// The has mapping.
            /// </summary>
            /// <param name="internalSubscriptionIdentifier">
            /// The internal subscription identifier.
            /// </param>
            /// <returns>
            /// The has mapping.
            /// </returns>
            public bool HasMapping(IFeedWiring internalSubscriptionIdentifier)
            {
                return this.externalPerInternal.ContainsKey(internalSubscriptionIdentifier);
            }

            /// <summary>
            /// The has mapping.
            /// </summary>
            /// <param name="externalSubscriptionIdentifier">
            /// The external subscription identifier.
            /// </param>
            /// <returns>
            /// The has mapping.
            /// </returns>
            public bool HasMapping(ExternalIdentifierType externalSubscriptionIdentifier)
            {
                return this.internalPerExternal.ContainsKey(externalSubscriptionIdentifier);
            }

            /// <summary>
            /// The set mapping.
            /// </summary>
            /// <param name="internalSubscriptionIdentifier">
            /// The internal subscription identifier.
            /// </param>
            /// <param name="externalSubscriptionIdentifier">
            /// The external subscription identifier.
            /// </param>
            public void SetMapping(IFeedWiring internalSubscriptionIdentifier, ExternalIdentifierType externalSubscriptionIdentifier)
            {
                this.externalPerInternal[internalSubscriptionIdentifier] = externalSubscriptionIdentifier;
                this.internalPerExternal[externalSubscriptionIdentifier] = internalSubscriptionIdentifier;
            }

            /// <summary>
            /// The unset mapping.
            /// </summary>
            /// <param name="internalSubscriptionIdentifier">
            /// The internal subscription identifier.
            /// </param>
            /// <param name="externalSubscriptionIdentifier">
            /// The external subscription identifier.
            /// </param>
            public void UnsetMapping(IFeedWiring internalSubscriptionIdentifier, ExternalIdentifierType externalSubscriptionIdentifier)
            {
                if (this.HasMapping(internalSubscriptionIdentifier))
                {
                    this.externalPerInternal.Remove(internalSubscriptionIdentifier);
                    this.internalPerExternal.Remove(externalSubscriptionIdentifier);
                }
            }

            #endregion
        }
    }
}